package cn.boodqian.airreport;

import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import junit.framework.Assert;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import cn.boodqian.util.GAERequest;
import cn.boodqian.util.Log;

import android.content.Context;
import android.content.SharedPreferences.Editor;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.widget.Toast;

public class GlobalData {
	
	// Static Data
	public static String[] gItemNameList = new String[] {"pm25", "pm10", "so2", "no2"};
	public static String[] gItemDisplayNameList = new String[] {"PM2.5", "PM10", "SO2", "NO2"};
	// http://www.wahart.com.hk/rgb.htm
	// CornflowerBlue, Gold, SeaGreen, IndianRed
	public static long[] gItemDisplayColorList = new long[] {0xff6495ED, 0xffFFD700, 0xff2E8B57, 0xffcd5c5c};
	public static String gGAEProject = "airdata2-feed";
	
	// Need to try best to keep these during update
	public ArrayList<String> gProfileList = null;
	
	// Normal saved data, could be lost during update
	public ArrayList<HashMap<String, String>> gLocationList = null;
	public HashMap<String, AirData> gAirdataHash = null;
	public int gLastServerMessageCode = 0;
	public long gLastUpdateMessageTime = 0;
	public int gLastLocationCode = 0; // Location version
	
	// No save data
	public boolean gLocationNeedUpdate = false;
	public boolean gIsFirstRun = false;
	
	// Private data
	private GAERequest mAirdataRequest = new GAERequest(GlobalData.gGAEProject);
	private SharedPreferences mPrefs = null;
	private PackageInfo mPackageInfo = null;
	//private Gson mGson = new GsonBuilder().setDateFormat(DateFormat.FULL).create();
	private Gson mGson = new GsonBuilder().setDateFormat("yyyy/MM/dd HH:mm:ss Z").create();
	
	private Activity mActivity = null;
	private static GlobalData mInstance = null;
	private Handler mHandler = new Handler();
	
	private GlobalData(Activity activity)
	{
		mActivity = activity;
		
		mPrefs = mActivity.getPreferences(Context.MODE_PRIVATE);
		try {
			mPackageInfo = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(), 0);
		} catch (NameNotFoundException e1) {
			Log.e("getPackageInfo failed");
			e1.printStackTrace();
			return;
		}
	}
	
	public static GlobalData getInstance()
	{
		return mInstance;
	}
	
	public static GlobalData getNewInstance(Activity activity)
	{
		mInstance = new GlobalData(activity);
		return mInstance;
	}
	
	public void restorePrefs()
	{	
		String strProfileList = mPrefs.getString("profile_list", "[]");
		gProfileList = mGson.fromJson(strProfileList, ArrayList.class);
		if (gProfileList!=null && gProfileList.size()>0) {
			Log.i("gProfileList Restored");
		} else {
			gProfileList = new ArrayList<String>();
		}
		
		String version = mPrefs.getString("version", "");
		if(mPackageInfo==null || mPackageInfo.versionName.compareTo(version)!=0) {
			Log.i(String.format("Version names (%s vs %s) do not match",version,mPackageInfo==null?"null":mPackageInfo.versionName));
			gIsFirstRun = true;
			return;
		}

		try {
			JsonParser parser = new JsonParser();
		    JsonArray array = parser.parse(mPrefs.getString("location_list", "")).getAsJsonArray();
		    gLocationList = new ArrayList<HashMap<String,String>>();
		    for(JsonElement e:array) {
		    	HashMap map = mGson.fromJson(e, HashMap.class);
		    	gLocationList.add(map);
		    }
			if (gLocationList != null && gLocationList.size()>0) {
				Log.i("gLocationList Restored");
			}
		} catch (Exception e) {
			gLocationList = new ArrayList<HashMap<String,String>>();
			e.printStackTrace();
		}

		try {
			String strAirdataHash = mPrefs.getString("airdata", "{}");
		    Type type = new TypeToken<HashMap<String, AirData>>() {}.getType();
			gAirdataHash = mGson.fromJson(strAirdataHash, type);
			if (gAirdataHash != null && gAirdataHash.size()>0) {
				Log.i( "gAirdataHash Restored");
				Log.i("gAirdataHash = " + gAirdataHash.keySet().toString());
			}
		} catch (Exception e) {
			gAirdataHash = null;
			e.printStackTrace();
		}
		gLastServerMessageCode = mPrefs.getInt("last_server_message_code", 0);
		gLastUpdateMessageTime = mPrefs.getLong("last_update_message_time", 0);
		gLastLocationCode = mPrefs.getInt("last_location_code", 0);
	}
	
	public void savePrefs()
	{
		try {
			Editor editor = mPrefs.edit();
			editor.putString("version", mPackageInfo.versionName);
			editor.putString("profile_list", mGson.toJson(gProfileList));
			editor.putString("location_list", mGson.toJson(gLocationList));
			editor.putString("airdata", mGson.toJson(gAirdataHash));
			editor.putInt("last_server_message_code", gLastServerMessageCode);
			editor.putLong("last_update_message_time", gLastUpdateMessageTime);
			editor.putInt("last_location_code", gLastLocationCode);
			editor.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void updateAirdata(final String [] idList, final Runnable run)
	{
		ConnectivityManager cm = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo ni = cm.getActiveNetworkInfo();
		if(  ni == null || !ni.isAvailable() || !ni.isConnected() ) { // Network unavailable
			Toast.makeText(
									mActivity,
									mActivity.getString(R.string.no_network),
									Toast.LENGTH_LONG).show();
			for( String id : idList) {
					AirData oldData = gAirdataHash.get(id);
					if( oldData == null ) gAirdataHash.put(id, new AirData());
			}
			if(run != null ) run.run();
			return;
		}
		
		if( idList ==null || idList.length == 0 ) {
			if(run != null ) run.run();
			return;
		}
		
		final ProgressDialog dlg = ProgressDialog.show(mActivity,
				mActivity.getString(R.string.refresh_wait),
				mActivity.getString(R.string.refresh_data), true);

		// Use thread to prevent any possible UI freezes
		Thread thd = new Thread() {
			@Override
			public void run() {
				if( gAirdataHash == null )
					gAirdataHash = new HashMap<String, AirData>();
				
				boolean isSuccess = true, hasUpdate = false;
				for( String id : idList) {
					AirData oldData = gAirdataHash.get(id);
					if( oldData != null && oldData.getTime() != null &&
							(new Date().getTime() - oldData.getTime().getTime())/1000 < 3600 + 1800 ) {
						// Skip the update if latest data is within 1.5 hours
						continue;
					}
					hasUpdate = true;
					String strNewData = mAirdataRequest.doGet("/airdata/"+id + "?hour=24");
					AirData newData = mGson.fromJson(strNewData, AirData.class);
					if( newData == null ) {
						newData = new AirData();
						isSuccess = false;
					}
					if( oldData!=null && oldData.getTime()!=null ) {
						gAirdataHash.get(id).merge(newData);
					} else {
						gAirdataHash.put(id, newData);
					}
				}
				
				final boolean success = isSuccess;
				final boolean update = hasUpdate;
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						if ( !success ) {
							Toast.makeText(
									dlg.getContext(),
									mActivity.getString(R.string.refresh_error) + "\n"
											+ mAirdataRequest.getLastError(),
									Toast.LENGTH_LONG).show();
						} else {
							Toast.makeText(dlg.getContext(),
									mActivity.getString( update ? R.string.refresh_done : R.string.refresh_toosoon),
									Toast.LENGTH_LONG).show();
						}
						dlg.dismiss();

						if ( run != null ) run.run();
					}
				});
			}
		};
		thd.start();
	}
	
	public String getVersionName()
	{
		return mPackageInfo.versionName;
	}
	
	public int getVersionCode()
	{
		return mPackageInfo.versionCode;
	}
	
	// Returns [Name, AQI, cc (mg/m^3)]
	public Object[] getMaxPollutant(String id)
	{
		AirData airdata = gAirdataHash.get(id);
		Assert.assertNotNull(id, airdata);
		
		Integer maxaqi = 0;
		Float maxcc = 0f;
		String maxname = "";
		for(String name:gItemNameList) {
			if(airdata.getData(name)==null || airdata.getData(name).size() == 0)
				continue;
			float cc = airdata.getData(name).get(airdata.getLength()-1);
			int aqi = AQI.getAQI(name, cc);
			if( aqi > maxaqi ) {
				maxaqi = aqi;
				maxcc = cc;
				maxname = name;
			}
		}
		return new Object[]{maxname, maxaqi, maxcc};
	}
	
	public static String getPollutantDisplayName(String name)
	{
		for(int i = 0; i < gItemNameList.length; i++) {
			if(name.compareTo(gItemNameList[i])==0)
				return gItemDisplayNameList[i];
		}
		return "";
	}
	
	public static long getPollutantDisplayColor(String name)
	{
		for(int i = 0; i < gItemNameList.length; i++) {
			if(name.compareTo(gItemNameList[i])==0)
				return gItemDisplayColorList[i];
		}
		return 0;
	}
	
	public HashMap<String, String> getLocationInfo(String id)
	{
		for(HashMap<String, String> location : gLocationList) {
			if( id.compareTo(location.get("id")) == 0) {
				return location;
			}
		}
		return null;
	}
	
	public Float calcAverage(String locId, String airType) {
		List<Float> list = gAirdataHash.get(locId).getData(airType);
		int index = list.size() - 1;
		int length = Math.min(24, list.size());
		
		float sum = 0.0f;
		int num = 0;
		for(int i=0;i<length;i++) {
			float value = list.get(index-i);
			if (Float.compare(value, 0f) > 0) {
				sum += value;
				num++;
			}
		}
		return num>0 ? (sum/num) : 0f;
	}
	
	public void clearAll() {
		gProfileList.clear();
		gLocationList.clear();
		gAirdataHash.clear();
	}
}
